﻿/// @file kdbgout.c
#include "stdafx.h"
#include "sbcs.h"
#include "extend/sberror.h"
#include <signal.h>

#if _SB_ANDROID_
#define AVOID_FORMAT_LITERAL	1
#include <android/log.h>
// 빌드할 때 liblog.so 링크 할 것
#endif

#define USE_OLV_PREFIX	1
#define USE_DBG_LOCK	1

#if USE_DBG_LOCK
#define DBG_LOCK			K_LOCK(k_impl_dbgout.lock)
#define DBG_UNLOCK			K_UNLOCK(k_impl_dbgout.lock)
#else
#define DBG_LOCK			kp_lock()
#define DBG_UNLOCK			kp_unlock()
#endif


//////////////////////////////////////////////////////////////////////////
// 출력 레벨

/// 출력 레벨 접두사.
/// @date 2013-12-22
/// @param v KLV_로 시작하는 kint형 값.
/// @return 레벨 문자.
char k_lvprefix(kint v)
{
	static char _k_olv_prefixs[] =
	{
		'A',
		'N',
		'R',
		'I',
		'W',
		'E',
		'C',
		'F',
		'U'
	};
	return _k_olv_prefixs[(ksize_t)v < K_COUNTOF(_k_olv_prefixs) ? v : K_COUNTOF(_k_olv_prefixs) - 1];
}

/// 레벨 문자열.
/// @date 2013-12-22
/// @param v KLV_로 시작하는 kint형 값.
/// @return 실패하면 "unknown"을 반환. 그렇지 아니면 레벨 문자열
const char* k_lvname(kint v)
{
	static const char* _k_olv_names[] =
	{
		"all",
		"internal",
		"result",
		"info",
		"warning",
		"error",
		"critical",
		"fatal",
		"unknown"
	};
	return _k_olv_names[(ksize_t)v < K_COUNTOF(_k_olv_names) ? v : K_COUNTOF(_k_olv_names) - 1];
}


//////////////////////////////////////////////////////////////////////////
// 출력 및 디버그

//
static struct kImplDbgOut
{
	kcham				isdbg;
	kint				level;

	kcham				ischar;
	kcham				logtime;

#if USE_DBG_LOCK
	kLock				lock;
#endif

#if _SB_WINDOWS_
	HANDLE				handle;
#else
	FILE*               fp;
#endif

	kchar				tag[32];
} k_impl_dbgout =
{
#if 0
#if _SB_DEBUG_ || _SB_ANDROID_
	TRUE,
#else
	FALSE,
#endif
#else
	TRUE,
#endif
	KLV_RESULT,

	TRUE,
	FALSE,

#if USE_DBG_LOCK
	KLOCK_INIT,
#endif

	NULL,

	"sb",
};

// 
void _k_dbgout_init(void)
{
#if _SB_WINDOWS_
#if _SB_WINDOWS_DESKTOP_
	k_impl_dbgout.handle = GetStdHandle(STD_OUTPUT_HANDLE);
#else
	k_impl_dbgout.handle = NULL;
#endif
#else
#if !_SB_MOBILE_
	k_impl_dbgout.fp = stdout;
#else
	k_impl_dbgout.fp = NULL;
#endif
#endif
	k_impl_dbgout.level = KLV_INFO;
}

// 
void _k_dbgout_disp(void)
{
#if _SB_WINDOWS_
	if (k_impl_dbgout.handle != NULL && !k_impl_dbgout.ischar)
		CloseHandle(k_impl_dbgout.handle);
#else
	if (k_impl_dbgout.fp != NULL && !k_impl_dbgout.ischar)
		fclose(k_impl_dbgout.fp);
#endif
}

// 콘솔 출력
static void _k_dbg_console_str(const char* s, kint len)
{
#if _SB_WINDOWS_
	if (k_impl_dbgout.handle != NULL)
	{
		DWORD wtn, dw = (DWORD)(len <= 0 ? k_strlen(s) : len);

#if _SB_WINDOWS_DESKTOP_
		if (GetFileType(k_impl_dbgout.handle) != FILE_TYPE_CHAR)
			WriteFile(k_impl_dbgout.handle, s, dw, &wtn, NULL);
		else if (WriteConsoleA(k_impl_dbgout.handle, s, dw, &wtn, NULL) == 0)
			WriteFile(k_impl_dbgout.handle, s, dw, &wtn, NULL);
#else
		WriteFile(k_impl_dbgout.handle, s, dw, &wtn, NULL);
#endif
	}
#else
	if (k_impl_dbgout.fp != NULL)
		fputs(s, k_impl_dbgout.fp);
#endif
}

// 문자 출력
static void _k_dbg_console_char(int ch)
{
#if _SB_WINDOWS_
	if (k_impl_dbgout.handle != NULL)
	{
		DWORD wtn;

#if _SB_WINDOWS_DESKTOP_
		if (GetFileType(k_impl_dbgout.handle) != FILE_TYPE_CHAR)
			WriteFile(k_impl_dbgout.handle, &ch, 1, &wtn, NULL);
		else if (WriteConsoleA(k_impl_dbgout.handle, &ch, 1, &wtn, NULL) == 0)
			WriteFile(k_impl_dbgout.handle, &ch, 1, &wtn, NULL);
#else
		WriteFile(k_impl_dbgout.handle, &ch, 1, &wtn, NULL);
#endif
	}
#else
	if (k_impl_dbgout.fp != NULL)
		fputc(ch, k_impl_dbgout.fp);
#endif
}

// 문자열 출력
static void _k_dbg_console_lstr(const kwchar* s, kint len)
{
#if _SB_WINDOWS_ 
	if (k_impl_dbgout.handle != NULL)
	{
		DWORD wtn, dw = (DWORD)(len <= 0 ? k_wcslen(s) : len);

#if _SB_WINDOWS_DESKTOP_
		if (GetFileType(k_impl_dbgout.handle) != FILE_TYPE_CHAR)
			WriteFile(k_impl_dbgout.handle, s, dw * sizeof(kwchar), &wtn, NULL);
		else if (WriteConsole(k_impl_dbgout.handle, s, dw, &wtn, NULL) == 0)
			WriteFile(k_impl_dbgout.handle, s, dw * sizeof(kwchar), &wtn, NULL);
#else
		WriteFile(k_impl_dbgout.handle, s, dw * sizeof(kwchar), &wtn, NULL);
#endif
	}
#else
	if (k_impl_dbgout.fp != NULL)
		fputws(s, k_impl_dbgout.fp);
#endif
}

// 출력
static void _k_dbg_write_str(const char* text, kint len)
{
	DBG_LOCK;
	_k_dbg_console_str(text, len);
	DBG_UNLOCK;

#if _SB_WINDOWS_
	if (k_impl_dbgout.isdbg)
		OutputDebugStringA(text);
#elif _SB_ANDROID_
	if (k_impl_dbgout.isdbg)
	{
#ifdef AVOID_FORMAT_LITERAL
		__android_log_print(ANDROID_LOG_VERBOSE, k_impl_dbgout.tag, "%s", text);
#else
		__android_log_print(ANDROID_LOG_VERBOSE, k_impl_dbgout.tag, text);
#endif
	}
#endif
}

// 출력
static void _k_dbg_write_char(int ch)
{
	DBG_LOCK;
	_k_dbg_console_char(ch);
	DBG_UNLOCK;

	if (k_impl_dbgout.isdbg)
	{
		char sz[2];
		sz[0] = (char)ch;
		sz[1] = '\0';

#if _SB_WINDOWS_
		OutputDebugStringA(sz);
#elif _SB_ANDROID_
#ifdef AVOID_FORMAT_LITERAL
		__android_log_print(ANDROID_LOG_VERBOSE, k_impl_dbgout.tag, "%s", sz);
#else
		__android_log_print(ANDROID_LOG_VERBOSE, k_impl_dbgout.tag, sz);
#endif
#endif
	}
}

// 출력
static void _k_dbg_write_lstr(const kwchar* text, kint len)
{
	DBG_LOCK;
	_k_dbg_console_lstr(text, len);
	DBG_UNLOCK;

	if (k_impl_dbgout.isdbg)
	{
#if _SB_WINDOWS_
		OutputDebugString(text);
#elif _SB_ANDROID_
		// len은 <= 0 일 수 있다.
		ksize_t need=k_conv_uni_to_utf8(NULL, 0, text, len);
		kchar* buff = (kchar*)alloca(need + 1);

		k_conv_uni_to_utf8(buff, need + 1, text, len);
#ifdef AVOID_FORMAT_LITERAL
		__android_log_print(ANDROID_LOG_VERBOSE, k_impl_dbgout.tag, "%s", buff);
#else
		__android_log_print(ANDROID_LOG_VERBOSE, k_impl_dbgout.tag, buff);
#endif
#endif
	}
}

// 출력
static void _k_dbg_write_trace(int level, const char* cls, const char* text)
{
#if USE_OLV_PREFIX
	char cp = k_lvprefix(level);
#else
	const char* lv = k_lvname(level);
#endif
	const char* fmt;
	char* buf;
	int size;

	if (!cls)
		cls = k_impl_dbgout.tag;

	if (k_impl_dbgout.logtime)
	{
		kDateVal dt;
		k_dateval_now(&dt);

#if USE_OLV_PREFIX
		fmt = "[%04d-%02d-%02d %02d:%02d:%02d.%03d %c/%s] %s\n";
		size = k_snprintf(NULL, 0, fmt, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond, cp, cls, text);
		buf = (char*)alloca(size + 1);
		k_snprintf(buf, size + 1, fmt, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond, cp, cls, text);
#else
		fmt = "[%04d-%02d-%02d %02d:%02d:%02d.%03d %s.%s] %s\n";
		size = k_snprintf(NULL, 0, fmt, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond, cls, lv, text);
		buf = (char*)alloca(size + 1);
		k_snprintf(buf, size + 1, fmt, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond, cls, lv, text);
#endif
	}
	else
	{
#if USE_OLV_PREFIX
		fmt = "[%c/%s] %s\n";
		size = k_snprintf(NULL, 0, fmt, cp, cls, text);
		buf = (char*)alloca(size + 1);
		k_snprintf(buf, size + 1, fmt, cp, cls, text);
#else
		fmt = "[%s.%s] %s\n";
		size = k_snprintf(NULL, 0, fmt, cls, lv, text);
		buf = (char*)alloca(size + 1);
		k_snprintf(buf, size + 1, fmt, cls, lv, text);
#endif
	}

	DBG_LOCK;
	_k_dbg_console_str(buf, size);
	DBG_UNLOCK;

	if (k_impl_dbgout.isdbg)
	{
#if _SB_WINDOWS_
		OutputDebugStringA(buf);
#elif _SB_ANDROID_
		static const int s_andid[] =
		{
			ANDROID_LOG_VERBOSE,
			ANDROID_LOG_DEBUG,
			ANDROID_LOG_VERBOSE,
			ANDROID_LOG_WARN,
			ANDROID_LOG_INFO,
			ANDROID_LOG_ERROR,
			ANDROID_LOG_ERROR,
			ANDROID_LOG_FATAL,
			ANDROID_LOG_INFO,
		};

		int andlevel = level < (int)K_COUNTOF(s_andid) ? s_andid[level] : s_andid[0];

		buf[size-1] = '\0';
#ifdef AVOID_FORMAT_LITERAL
		__android_log_print(andlevel, cls ? cls : k_impl_dbgout.tag, "%s", buf);
#else
		__android_log_print(andlevel, cls ? cls : k_impl_dbgout.tag, buf);
#endif
#endif
	}

	//
	if (level == KLV_FATAL)
	{
#if _SB_WINDOWS_DESKTOP_
		kwchar* uni;
		size = (kint)k_getuni(NULL, 0, text);
		uni = (kwchar*)alloca((size + 1) * sizeof(kwchar));
		k_getuni(uni, size + 1, text);

		MessageBox(NULL, uni, NULL, MB_ICONERROR | MB_SETFOREGROUND);
#endif

		if (k_candebug())
			k_breakpoint();

		abort();
	}
}


//////////////////////////////////////////////////////////////////////////
// 아웃풋

/// 문자열 출력.
/// @date 2013-12-22
/// @param s 출력할 문자열
/// @return 출력한 길이.
void k_output(const char* s)
{
	k_init();

	DBG_LOCK;
	_k_dbg_console_str(s, 0);
	DBG_UNLOCK;
}

/// 문자열 출력, 포맷.
/// @date 2013-12-22
/// @param fmt 문자열 포맷.
/// @return 출력한 길이.
void k_outputf(const char* fmt, ...)
{
	int ret;
	char* buf;
	va_list va;

	k_init();

	va_start(va, fmt);
	ret = k_vsnprintf(NULL, 0, fmt, va);
	va_end(va);

	buf = (char*)alloca(ret + 1);
	va_start(va, fmt);
	k_vsnprintf(buf, ret + 1, fmt, va);
	va_end(va);

	DBG_LOCK;
	_k_dbg_console_str(buf, ret);
	DBG_UNLOCK;
}

/// 문자 쓰기.
/// @date 2013-12-22
/// @param ch 출력할 문자
void k_outputch(kint ch)
{
	k_init();

	DBG_LOCK;
	_k_dbg_console_char(ch);
	DBG_UNLOCK;
}

/// 유니코드 문자열 쓰기.
/// @date 2013-12-22
/// @param s 출력할 유니코드 문자열
/// @return 출력한 길이
void k_outputl(const kwchar* s)
{
#if _SB_WINDOWS_
	k_init();

	DBG_LOCK;
	_k_dbg_console_lstr(s, 0);
	DBG_UNLOCK;
#else
	// 유니코드 출력은 윈도우 전용
	DBG_LOCK;
	_k_dbg_console_str("[no unicode output support]", 0);
	DBG_UNLOCK;
#endif
}

/// UTF-8 문자열 출력.
/// @date 2013-12-22
/// @param s 출력할 UTF-8 문자열
/// @return 출력한 길이
void k_output_utf8(const char* s)
{
#if _SB_WINDOWS_
	kwchar* uni;
	kint ret;

	k_init();

	ret = (kint)k_conv_utf8_to_utf16(NULL, 0, s, 0);
	uni = (kwchar*)alloca((ret + 1) * sizeof(kwchar));
	k_conv_utf8_to_utf16(uni, ret + 1, s, 0);

	DBG_LOCK;
	_k_dbg_console_lstr(uni, ret);
	DBG_UNLOCK;
#else
	k_output(s);
#endif
}

/// UTF-8 문자열 출력, 포맷.
/// @date 2013-12-22
/// @param fmt 문자열 포맷.
/// @return 출력한 길이
void k_outputf_utf8(const char* fmt, ...)
{
	int ret;
	char* buf;
	va_list va;
#if _SB_WINDOWS_
	kwchar* uni;
#endif

	k_init();

	va_start(va, fmt);
	ret = k_vsnprintf(NULL, 0, fmt, va);
	va_end(va);

	buf = (char*)alloca(ret + 1);
	va_start(va, fmt);
	k_vsnprintf(buf, ret + 1, fmt, va);
	va_end(va);

#if _SB_WINDOWS_
	ret = (kint)k_conv_utf8_to_utf16(NULL, 0, buf, 0);
	uni = (kwchar*)alloca((ret + 1) * sizeof(kwchar));
	k_conv_utf8_to_utf16(uni, ret + 1, buf, 0);

	DBG_LOCK;
	_k_dbg_console_lstr(uni, ret);
	DBG_UNLOCK;
#else
	DBG_LOCK;
	_k_dbg_console_str(buf, ret);
	DBG_UNLOCK;
#endif
}


//////////////////////////////////////////////////////////////////////////
// 출력

/// 출력 레벨을 얻는다.
/// @date 2013-12-22
/// @return 출력 레벨.
kint k_getlv(void)
{
	return k_impl_dbgout.level;
}

/// 출력 레벨을 쓴다
/// @date 2013-12-22
/// @param value 출력 레벨.
void k_setlv(kint value)
{
	k_return_if_fail((kuint)value <= KLV_NONE);
	k_init();
	k_impl_dbgout.level = value;
}

/// 현재 설정된 태그를 얻는다.
/// @date 2013-12-22
/// @return 현재 설정된 태그.
const char* k_gettag(void)
{
	return k_impl_dbgout.tag;
}

/// 태그를 설정한다.
/// @date 2013-12-22
/// @param str_len_32 문자열 길이 32의 태그
void k_settag(const char* str_len_32)
{
	k_init();

	DBG_LOCK;

	if (str_len_32)
		k_strncpy(k_impl_dbgout.tag, str_len_32, 32 - 1);
	else
	{
		k_impl_dbgout.tag[0] = 's';
		k_impl_dbgout.tag[1] = 'b';
		k_impl_dbgout.tag[2] = '\0';
	}

	DBG_UNLOCK;
}

/// 디버그 출력.
/// @date 2013-12-22
/// @param text 문자열.
void k_mesg(const char* text)
{
	k_init();
	_k_dbg_write_str(text, 0);
}

/// 디버그 출력, 글자.
/// @date 2013-12-22
/// @param ch 글자.
void k_mesgch(int ch)
{
	k_init();
	_k_dbg_write_char(ch);
}

/// 디버그 출력, 포맷.
/// @date 2013-12-22
/// @param fmt 포맷 문자열.
void k_mesgf(const char* fmt, ...)
{
	char* buf;
	int len;
	va_list va;

	k_init();

	va_start(va, fmt);
	len = k_vsnprintf(NULL, 0, fmt, va);
	va_end(va);

	buf = (char*)alloca(len + 1);
	va_start(va, fmt);
	k_vsnprintf(buf, len + 1, fmt, va);
	va_end(va);

	_k_dbg_write_str(buf, len);
}

/// 디버그 출력, 유니코드.
/// @date 2013-12-22
/// @param text 유니코드 문자열.
void k_mesgl(const kwchar* text)
{
	k_init();
	_k_dbg_write_lstr(text, 0);
}

/// 디버그 출력, UTF-8
/// @date 2013-12-22
/// @param s UTF-8 문자열.
void k_mesg_utf8(const char* s)
{
#if _SB_WINDOWS_
	kwchar* uni;
	int len;

	k_init();

	len = (kint)k_conv_utf8_to_utf16(NULL, 0, s, 0);
	uni = (kwchar*)alloca((len + 1) * sizeof(kwchar));
	k_conv_utf8_to_utf16(uni, len + 1, s, 0);
	_k_dbg_write_lstr(uni, len);
#else
	k_mesg(s);
#endif
}

/// 디버그 출력, 포맷, UTF-8.
/// @date 2013-12-22
/// @param fmt UTF-8 포맷 문자열.
void k_mesgf_utf8(const char* fmt, ...)
{
	char* buf;
	int len;
	va_list va;
#if _SB_WINDOWS_
	kwchar* uni;
#endif

	k_init();

	va_start(va, fmt);
	len = k_vsnprintf(NULL, 0, fmt, va);
	va_end(va);

	buf = (char*)alloca(len + 1);
	va_start(va, fmt);
	k_vsnprintf(buf, len + 1, fmt, va);
	va_end(va);

#if _SB_WINDOWS_
	len = (kint)k_conv_utf8_to_utf16(NULL, 0, buf, 0);
	uni = (kwchar*)alloca((len + 1) * sizeof(kwchar));
	k_conv_utf8_to_utf16(uni, len + 1, buf, 0);
	_k_dbg_write_lstr(uni, len);
#else
	_k_dbg_write_str(buf, len);
#endif
}

/// 트레이스, 시스템.
/// @date 2013-12-22
/// @param text 문자열.
void k_mesgs(const char* text)
{
	char sys[260];
	char* buf;
	int len;

	k_syserr(sys, 260 - 1, 0);

	if (!text)
		_k_dbg_write_str(sys, 0);
	else
	{
		len = k_snprintf(NULL, 0, "[%s] %s", sys, text);
		buf = (char*)alloca(len + 1);
		k_snprintf(buf, len + 1, "[%s] %s", sys, text);
		_k_dbg_write_str(buf, len);
	}
}

/// 트레이스.
/// @date 2013-12-22
/// @param level 레벨.
/// @param cls   클래스.
/// @param text  문자열.
void k_mesg_trace(kint level, const char* cls, const char* text)
{
	k_init();
	_k_dbg_write_trace(level, cls, text);
}

/// 트레이스, 포맷.
/// @date 2013-12-22
/// @param level 레벨.
/// @param cls   클래스.
/// @param fmt   포맷.
void k_mesgf_trace(kint level, const char* cls, const char* fmt, ...)
{
	char* buf;
	int len;
	va_list va;

	k_init();

	va_start(va, fmt);
	len = k_vsnprintf(NULL, 0, fmt, va);
	va_end(va);

	buf = (char*)alloca(len + 1);
	va_start(va, fmt);
	k_vsnprintf(buf, len + 1, fmt, va);
	va_end(va);

	_k_dbg_write_trace(level, cls, buf);
}

/// 트레이스, 시스템.
/// @date 2013-12-22
/// @param level 레벨.
/// @param cls   클래스.
/// @param text  텍스트 문자열.
void k_mesgs_trace(kint level, const char* cls, const char* text)
{
	char sys[260];
	char* buf;
	int len;

	k_syserr(sys, 260 - 1, 0);

	if (!text)
		_k_dbg_write_trace(level, cls, sys);
	else
	{
		len = k_snprintf(NULL, 0, "'%s' %s", sys, text);
		buf = (char*)alloca(len + 1);
		k_snprintf(buf, len + 1, "'%s' %s", sys, text);
		_k_dbg_write_trace(level, cls, buf);
	}
}

/// 오류 코드만 이용하는 빠른 트레이스 메시지 
/// @date 2013-12-22
/// @param level    레벨.
/// @param cls	    클래스.
/// @param error    오류 코드.
/// @param filename 파일 이름.
/// @param line	    줄번호.
void k_mesgp_trace(kint level, const char* cls, kint error, const char* filename, kint line)
{
	char tmp[128];
	const char* msg;

	k_init();

	// 오류 코드
	k_serrn(error);

	// 오류 메시지
	if (error != 0)
		msg = k_errmsg(error);
	else
	{
		// 시스템 오류로 친다
		k_syserr(tmp, 128 - 1, 0);
		msg = tmp;
	}

	// 메시지 만들고
	if (!filename)
		_k_dbg_write_trace(level, cls, msg);
	else
	{
		// 파일 이름까지 출력
		// 오류 메시지+파일이름까지 저장하긴 하는데... MAX_PATH로 함
		char sz[K_MAX_PATH];
		k_snprintf(sz, K_MAX_PATH - 1, "%s (%s:%d)", msg, filename, line);
		_k_dbg_write_trace(level, cls, sz);
	}
}

/// 메시지 출력하는 트레이스 메시지 
/// @date 2014-01-06
/// @param level    레벨.
/// @param cls	    클래스.
/// @param text     텍스트.
/// @param filename 파일 이름.
/// @param line	    줄번호.
void k_mesgt_trace(kint level, const char* cls, kint error, const char* text, const char* filename, kint line)
{
	k_init();

	// 오류 코드
	k_serrn(error);

	// 오류 메시지
	if (!text)
		text = k_errmsg(KERR_UNKNOWN);

	// 메시지 만들고
	if (!filename)
		_k_dbg_write_trace(level, cls, text);
	else
	{
		// 파일 이름까지 출력
		// 오류 메시지+파일이름까지 저장하긴 하는데... MAX_PATH로 함
		char sz[K_MAX_PATH];
		k_snprintf(sz, K_MAX_PATH - 1, "%s (%s:%d)", text, filename, line);
		_k_dbg_write_trace(level, cls, sz);
	}
}

/// 오류처리용 assert.
/// @date 2013-12-22
/// @param expr	    표현식.
/// @param mesg	    메시지.
/// @param filename 파일 이름.
/// @param line	    줄번호.
/// @return 정수 값.
kint k_mesg_assert(const char* expr, const char* mesg, const char* filename, kint line)
{
	char* buf;
	int len;

	k_init();

	len = k_snprintf(NULL, 0, "filename=\"%s\", line=%d", filename, line);
	buf = (char*)alloca(len + 1);
	k_snprintf(buf, len + 1, "filename=\"%s\", line=%d", filename, line);
	_k_dbg_write_trace(KLV_CRITICAL, "k.assert", buf);

	if (mesg)
		_k_dbg_write_trace(KLV_NONE, "k.assert", mesg);

	if (expr)
		_k_dbg_write_trace(KLV_NONE, "k.assert", expr);

	return 0;
}

/// 오류처리용 메시지 출력.
/// @date 2013-12-22
/// @param ew	    오류 또는 경고.
/// @param col	    컬럼.
/// @param line	    줄번호.
/// @param filename 파일 이름.
/// @param code	    코드.
/// @param pass	    패스.
/// @param text	    텍스트 문자열.
/// @return 정수 값.
kint k_mesg_error(kcham ew, kint col, kint line, const char* filename, kint code, const char* pass, const char* text)
{
	char codemesg[24];
	char* buf;
	int len;

	k_init();

	if (filename == NULL)
		filename = "unknown";

	if (text == NULL)
	{
		text = codemesg;
		k_snprintf(codemesg, 24 - 1, "code=%X", code);
	}

#if _SB_WINDOWS_
	// ms 스타일
	len = k_snprintf(NULL, 0, "%s(%d:%d) : %s %d: %s\n", filename, col, line, (ew) ? "error" : "warning", code, text);
	buf = (char*)alloca(len + 1);
	k_snprintf(buf, len + 1, "%s(%d:%d) : %s %d: %s\n", filename, col, line, (ew) ? "error" : "warning", code, text);
	_k_dbg_write_str(buf, len);
	/**/
	pass;
#else
	// 유닉스 스타일
	len = k_snprintf(NULL, 0, "in %s(%d:%d), [%d] %s\n", filename, col, line, code, text);
	buf = (char*)alloca(len + 1);
	k_snprintf(buf, len + 1, "in %s(%d:%d), [%d] %s\n", filename, col, line, code, text);
	_k_dbg_write_trace(ew ? KLV_ERROR : KLV_WARNING, pass ? pass : "k", buf);
#endif

	return 0;
}

/// 버퍼에 에러메시지 넣기, [ST]ore [B]uffer by [M]essage.
/// @date 2013-12-22
/// @param [in,out] retbuf 받을 버퍼.
/// @param size			   크기.
/// @param ew			   오류 또는 경고.
/// @param col			   컬럼.
/// @param line			   줄번호.
/// @param filename		   파일 이름.
/// @param code			   코드.
/// @param pass			   패스.
/// @param text			   텍스트 문자열.
/// @return 정수 값.
KAPI kint k_stbm_error(char* retbuf, kint size, kcham ew, kint col, kint line, const char* filename, kint code, const char* pass, const char* text)
{
	char codemesg[24];

	k_init();

	if (filename == NULL)
		filename = "unknown";

	if (text == NULL)
	{
		text = codemesg;
		k_snprintf(codemesg, 24 - 1, "code=%X", code);
	}

#if _SB_WINDOWS_
	// ms 스타일
	return k_snprintf(retbuf, size, "%s(%d:%d) : %s %d: %s\n", filename, col, line, (ew) ? "error" : "warning", code, text);
	/**/
	pass;
#else
	// 유닉스 스타일
	return k_snprintf(retbuf, size, "in %s(%d:%d), [%d] %s\n", filename, col, line, code, text);
#endif
}

/// HALT 메시지.
/// @date 2013-12-22
/// @param cls 클래스.
/// @param msg 메시지.
void k_mesg_halt(const char* cls, const char* msg)
{
	const char* fmt;
	char* buf;
	int len;
	kDateTime dt;

	k_init();

	if (!cls)
		cls = k_impl_dbgout.tag;

	if (k_impl_dbgout.logtime)
	{
		k_now(&dt);

		fmt = "HALT [%04d-%02d-%02d %02d:%02d:%02d.%03d %s] %s\n";
		len = k_snprintf(NULL, 0, fmt, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond, cls, msg);
		buf = (char*)alloca(len + 1);
		k_snprintf(buf, len + 1, fmt, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond, cls, msg);
	}
	else
	{
		if (msg)
		{
			fmt = "HALT [%s] %s\n";
			len = k_snprintf(NULL, 0, fmt, cls, msg);
			buf = (char*)alloca(len + 1);
			k_snprintf(buf, len + 1, fmt, cls, msg);
		}
		else
		{
			fmt = "HALT [%s]\n";
			len = k_snprintf(NULL, 0, fmt, cls);
			buf = (char*)alloca(len + 1);
			k_snprintf(buf, len + 1, fmt, cls);
		}
	}

	_k_dbg_write_str(buf, len);

	if (k_candebug())
		k_breakpoint();

	k_exit(255);
}

/// 출력할 때 시간도 출력하는가?
/// @date 2013-12-22
/// @param value 값.
void k_output_log_time(kcham value)
{
	k_impl_dbgout.logtime = value;
}

/// 출력을 다른 곳으로 전환. 파일이나 표준 입출력 등.
/// @date 2013-12-22
/// @param filename 파일 이름. 이 값이 NULL 이면 표준입출력이고, stdour, stderr을 사용할 수 있다.
void k_output_redirect(const char* filename)
{
	kDateTime dt;
	char sz[260];
	char* psz;

	k_init();

	DBG_LOCK;

#if _SB_WINDOWS_
	if (!k_impl_dbgout.ischar)
		CloseHandle(k_impl_dbgout.handle);

	if (!filename || k_strieqv(filename, "stdout") || k_strieqv(filename, "stdin"))
	{
		k_impl_dbgout.ischar = TRUE;
#if _SB_WINDOWS_DESKTOP_
		k_impl_dbgout.handle = GetStdHandle(STD_OUTPUT_HANDLE);
#else
		k_impl_dbgout.handle = NULL;
#endif
	}
	else if (k_strieqv(filename, "stderr"))
	{
		k_impl_dbgout.ischar = TRUE;
#if _SB_WINDOWS_DESKTOP_
		k_impl_dbgout.handle = GetStdHandle(STD_ERROR_HANDLE);
#else
		k_impl_dbgout.handle = NULL;
#endif
	}
	else
	{
		HANDLE h;
		LARGE_INTEGER ll;
		kwchar wz[260];
#if !_SB_WINDOWS_DESKTOP_
		CREATEFILE2_EXTENDED_PARAMETERS cep;
#endif

		k_conv_utf8_to_utf16(wz, 260 - 1, filename, 0);

#if _SB_WINDOWS_DESKTOP_
		h = CreateFile(wz, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
#else
		k_zero_1(&cep);
		cep.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
		h = CreateFile2(wz, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_ALWAYS, &cep);
#endif

		if (h == INVALID_HANDLE_VALUE)
		{
			// 실패하면 표준 출력으로
			k_impl_dbgout.ischar = TRUE;
#if _SB_WINDOWS_DESKTOP_
			k_impl_dbgout.handle = GetStdHandle(STD_OUTPUT_HANDLE);
#else
			k_impl_dbgout.handle = NULL;
#endif
		}
		else
		{
			k_impl_dbgout.ischar = FALSE;
			k_impl_dbgout.handle = h;

			ll.QuadPart = 0;
			SetFilePointerEx(h, ll, &ll, FILE_END);
		}
	}
#else
	if (k_impl_dbgout.fp != NULL && !k_impl_dbgout.ischar)
		fclose(k_impl_dbgout.fp);

	if (!filename || k_strieqv(filename, "stdout") || k_strieqv(filename, "stdin"))
	{
		k_impl_dbgout.ischar = TRUE;
#if !_SB_MOBILE_
		k_impl_dbgout.fp = stdout;
#else
		k_impl_dbgout.fp = NULL;
#endif
	}
	else if (k_strieqv(filename, "stderr"))
	{
		k_impl_dbgout.ischar = TRUE;
#if !_SB_MOBILE_
		k_impl_dbgout.fp = stderr;
#else
		k_impl_dbgout.fp = NULL;
#endif
	}
	else
	{
		FILE* fp = fopen(filename, "a+");

		if (!fp)
		{
			k_impl_dbgout.ischar = TRUE;
#if !_SB_MOBILE_
			k_impl_dbgout.fp = stdout;
#else
			k_impl_dbgout.fp = NULL;
#endif
		}
		else
		{
			k_impl_dbgout.ischar = FALSE;
			k_impl_dbgout.fp = fp;

			fseek(fp, 0, SEEK_END);
		}
	}
#endif

	_k_dbg_console_char('\n');

	DBG_UNLOCK;

	//
	k_now(&dt);

	psz = k_strpcpy(sz, "output redirect");

	if (!k_impl_dbgout.logtime)
		k_snprintf(psz, 200 - 1, " on %04d-%02d-%02d %02d:%02d:%02d.%03d", dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond);

	_k_dbg_write_trace(KLV_INFO, NULL, sz);
}


//////////////////////////////////////////////////////////////////////////
// 디버그 핸들링

#if _MSC_VER && _SB_WINDOWS_DESKTOP_
typedef BOOL(WINAPI* stub_MiniDumpFunc)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, CONST PMINIDUMP_EXCEPTION_INFORMATION, CONST PMINIDUMP_USER_STREAM_INFORMATION, CONST PMINIDUMP_CALLBACK_INFORMATION);

// 미니 덤프 함수 준비
static stub_MiniDumpFunc _k_dbg_prep_mini_dump_func(void)
{
	stub_MiniDumpFunc func = NULL;
	HMODULE dll = LoadLibrary(L"dbghelp");

	if (dll)
	{
		func = (stub_MiniDumpFunc)GetProcAddress(dll, "MiniDumpWriteDump");

		if (!func)
			FreeLibrary(dll);
	}

	return func;
}

// 미니 덤프
static LONG _stdcall _k_dbg_mini_dump_filter(PEXCEPTION_POINTERS e)
{
	stub_MiniDumpFunc mdfn;
	kwchar tmp[MAX_PATH];
	kwchar mdn[MAX_PATH];
	kDateTime dt;
	HANDLE process;
	HANDLE hf;
	DWORD dw;
	MINIDUMP_EXCEPTION_INFORMATION di;

	// 함수가 있으면 고고씽
	if ((mdfn = _k_dbg_prep_mini_dump_func()) != NULL)
	{
		k_now(&dt);
		k_snwprintf(tmp, MAX_PATH - 1, L".%08X%08X", dt.date, dt.time);

		GetModuleFileName(NULL, mdn, MAX_PATH - 1);
		k_wcscat(mdn, tmp);
		k_wcscat(mdn, L".dmp");

		process = GetCurrentProcess();
		dw = GetCurrentProcessId();
		hf = CreateFile(mdn, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

		di.ExceptionPointers = e;
		di.ClientPointers = FALSE;
		di.ThreadId = GetCurrentThreadId();

		mdfn(process, dw, hf, MiniDumpNormal, e ? &di : NULL, NULL, NULL);
	}

	// 예의상 멘트 날려 줌
	k_snwprintf(tmp, 260 - 1, L"catch application error!.\nplease contact QC service.\n\n[@=0x%08X]", e->ExceptionRecord->ExceptionCode);
	MessageBox(NULL, tmp, L"application exception", MB_OK | MB_SETFOREGROUND);

	return EXCEPTION_EXECUTE_HANDLER;
}
#endif

/// 오류 발생 처리기를 활성화한다 (윈도우=MiniDump)
/// @date 2013-12-22
void k_apphandler(void)
{
	k_init();

#if _MSC_VER && _SB_WINDOWS_DESKTOP_
	SetUnhandledExceptionFilter(_k_dbg_mini_dump_filter);
#endif
}

/// 시스템 오류 메시지를 가져온다.
/// @date 2013-12-22
/// @param err		    오류.
/// @param [in,out] buf 널값이 아니면, 버퍼.
/// @param size		    크기.
/// @param isutf8	    UTF-8로 가져오려면 참.
/// @return 성공하면 참을 실패하면 거짓을 반환한다.
kcham _k_syserr_value(kint err, char* buf, ksize_t size, kcham isutf8)
{
#if _SB_WINDOWS_
	DWORD dw;
	kwchar uni[1024];

	k_return_value_if_fail(buf != NULL, FALSE);

	dw = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
		(DWORD)err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), uni, 1024 - 1, NULL);

	if (dw == 0)
		k_strncpy(buf, "unknown error", size);
	else
	{
		uni[dw - 1] = L'\0';
		k_wcsrem(uni, L"\n\r");

		if (isutf8)
			k_conv_utf16_to_utf8(buf, size, uni, 0);
		else
			k_getasc(buf, size, uni);
	}

	return TRUE;
#else
	char* psz;

	k_return_value_if_fail(buf != NULL, FALSE);

	psz = strerror(err);

	if (!psz)
		k_strncpy(buf, "unknown error", size);
	else
		k_strncpy(buf, psz, size);

	return TRUE;
#endif
}

/// 시스템 오류를 가져온다.
/// @date 2013-12-22
/// @param [in,out] buf 널값이 아니면, 버퍼.
/// @param size		    크기.
/// @param code_or_zero 코드 또는 0. 0이면 마지막 오류 코드를 메시지로 만들어 준다
/// @return 성공하면 참을 실패하면 거짓을 반환한다.
kcham k_syserr(char* buf, ksize_t size, kint code_or_zero)
{
#if _SB_WINDOWS_
	return _k_syserr_value(code_or_zero > 0 ? code_or_zero : GetLastError(), buf, size, FALSE);
#else
	return _k_syserr_value(code_or_zero > 0 ? code_or_zero : errno, buf, size, FALSE);
#endif
}

/// 시스템 오류, UTF-8 버전
/// @date 2013-12-22
/// @param [in,out] buf 널값이 아니면, 버퍼.
/// @param size		    크기.
/// @param code_or_zero 코드 또는 0. 0이면 마지막 오류 코드를 메시지로 만들어 준다
/// @return 성공하면 참을 실패하면 거짓을 반환한다.
kcham k_syserr_utf8(char* buf, ksize_t size, kint code_or_zero)
{
#if _SB_WINDOWS_
	return _k_syserr_value(code_or_zero > 0 ? code_or_zero : GetLastError(), buf, size, TRUE);
#else
	return _k_syserr_value(code_or_zero > 0 ? code_or_zero : errno, buf, size, TRUE);
#endif
}
